﻿/*
 * SRSTrasform
 * 
 * initial implementation : 20.11.2008
 *
 * Copyright (c) Adam Sida, asida@centrum.cz.
 * All rights reserved.
 * 
 * for license see Petra.Gis.License.txt
 */
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Petra.Gis.Geometry;
using Petra.Gis.Geometry.Support;
using Petra.Gis.ReferenceSystemServices;


namespace Petra.Gis.ReferenceSystemServices.Transformations
{

  /// <summary>
  /// Root abstract class for all transformations.
  /// Each transform is defined by source and target coordinate system.
  /// <c>SRSTransform</c> defines basic functionality that all transforms
  /// must implement plus defines functions for all 
  /// Petra.Gis.Geometry geometry types to let them transform.
  /// </summary>
  [DataContract(Name="TRANSFORM")]
  [KnownType(typeof(ProjectedCoordinateSystem))]
  [KnownType(typeof(GeographicCoordinateSystem))]
  [KnownType(typeof(GeocentricCoordinateSystem))]
  public abstract partial class SRSTransform
  {




    #region constructors


    /// <summary>
    /// xaml constr.
    /// </summary>
    protected SRSTransform() { }





    protected SRSTransform(CoordinateSystem sourceCS, CoordinateSystem targetCS)
    {
      if (sourceCS == null || targetCS == null)
        throw new ArgumentException("source or target CS is null");

      _sourceCS = sourceCS;
      _targetCS = targetCS;

      UpdateTransform();
    }

    #endregion constructors




    private CoordinateSystem _sourceCS;

    /// <summary>
    /// source coordinate system to transform data from
    /// </summary>
    [DataMember(Name="SRC")]
    public virtual CoordinateSystem Source
    {
      get { return _sourceCS; }
      set 
      {
        _sourceCS = value;
        UpdateTransform();
      }
    }





    private CoordinateSystem _targetCS;

    /// <summary>
    /// target coordinate system to transform data into
    /// </summary>
    [DataMember(Name = "TARGET")]
    public virtual CoordinateSystem Target
    {
      get { return _targetCS; }
      set 
      {
        _targetCS = value;
        UpdateTransform();
      }
    }





    /// <summary>
    /// sets or returns Metadata for current transformation
    /// </summary>
    [DataMember(Name = "INFO")]
    public Metadata Metadata
    { get; set; }




    /// <summary>
    /// must implement in inhrited classes
    /// returns true, when specific transformation is inversible.
    /// Returns false when specific transformation is not inversible.
    /// </summary>
    public abstract bool IsInversible { get; }






    /// <summary>
    /// if specific instance is inversible
    /// returns new instance of this transformation but inverted.
    /// Inverse is when transforms from target to source coordinate system
    /// </summary>
    /// <returns></returns>
    public abstract SRSTransform Inverse();






    /// <summary>
    /// Returns true if this transformation is inverted.
    /// For example map projections define forward projection as projection to geographic, and backwards
    /// as geographics to projection.
    /// </summary>
    public virtual bool IsInverse
    { get; protected set; }






    /// <summary>
    /// returns how many dimensions needs transform for computation of target coordinate
    /// this is for case when data CoordinateList is in 2d and transform computes in 3d
    /// then coordinate list must be converted to 3d before transform.
    /// Must be implemented in inherited classes
    /// </summary>
    public abstract Dimension TransformDimension {get;}





    /// <summary>
    /// must be implemented in inherited classes. This is basic function that
    /// transforms <c>ICoordinate</c> only. For better performance with some time critical
    /// operations it is suggested to implement full geometry model within inherited transform class.
    /// </summary>
    /// <param name="point">Coordinate to transform</param>
    protected abstract void transform(ICoordinate coord);





    /// <summary>
    /// transforms single Coordinate from source to target coordinate system
    /// </summary>
    /// <param name="from">Coordinate to transform</param>
    public virtual void Transform(Coordinate coord)
    {
      //transform(from, Dimension.dim3D);
      transform(coord);
    }




    /// <summary>
    /// transforms single Coordinate from source to target coordinate system
    /// </summary>
    /// <param name="from">Coordinate to transform</param>
    public virtual void Transform(ICoordinate coord)
    {
      //transform(from, Dimension.dim3D);
      transform(coord);
    }





    /// <summary>
    /// transforms input CoordinateList coordinates from source to target coordinate system
    /// </summary>
    /// <param name="coordList">CoordinateList to transform</param>
    public virtual void Transform(CoordinateList coordList)
    {

      foreach (ICoordinate coord in coordList)
      {
        //transform(from, coordList.dimension);
        transform(coord);
      }
      
    }


    
    
    /// <summary>
    /// Transforms single CoordStruct structure from source to destination coordinate system
    /// This function (only) must return value as CoordStruct is structure.
    /// </summary>
    /// <param name="point">point to transform</param>
    /// <returns>new instance of <c>CoordStruct</c> with transformed coordinates</returns>
    public virtual CoordStruct Transform(CoordStruct point) //by val default
    {
      // input CoordStruct is passed by value
      // variable point is therefore local copy
      Coordinate tmp = new Coordinate(point.X, point.Y, point.Z);

      transform(tmp);

      point.X = tmp.X;
      point.Y = tmp.Y;
      point.Z = tmp.Z;

      //CoordStruct is structure, so return value is more logical here...
      return point;
    
    }





    /// <summary>
    /// This method must provide necessary computations
    /// when transform is somehow changed (eg. parameters change,...)
    /// must implement in inherited classes. 
    /// </summary>
    protected abstract void updateTransform();





    /// <summary>
    /// Updates transform manually when any non deterministic value changes.
    /// This can happend for example, when single Projection parameter in Parameters collection
    /// changes (whole collection remains same). Also when Transform have aditional 
    /// math equations independent from source and target coordinate system or on normal parameters.
    /// </summary>
    public void UpdateTransform()
    {
      if (IsValid())
        updateTransform();
    }


    
    
    
    /// <summary>
    /// Determines whether transform is ready to use.
    /// Even if transform is not valid, transform function might work and produce
    /// some (probably invalid) result. Transforms uses weak error control for better performance.
    /// </summary>
    /// <returns>true when transformation is ready to use, false otherwise</returns>
    public bool IsValid()
    {
      return !(this.Source == null || this.Target == null) && graphCompleted();
    }





    /// <summary>
    /// object graph completed maintans object state during object initialization.
    /// this is support method for xaml and json
    /// </summary>
    /// <returns>true when all object properties are valid and object is ready to use, false when incomplete</returns>
    protected abstract bool graphCompleted();





  }
}
